package com.fand.generator.template;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.CollectionUtils;

import com.fand.generator.mapper.BusinessMapper;
import com.fand.generator.mapper.ModuleMapper;
import com.fand.generator.mapper.PoMapper;
import com.fand.generator.mapper.ProjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;

public class DefaultTemplate implements Template {
	private final static Log log = LogFactory.getLog(DefaultTemplate.class);

	private Configuration configuration;

	private final Map<String, String> contextMap = Maps.newHashMap();

	private String extendName = "Oracle";

	private final Map<String, Object> model = Maps.newHashMap();

	private String outputDir;

	private boolean overrideFile = true;

	private Map<String, PoMapper> poMapperMap;

	private ProjectMapper projectMapper;

	private final List<String> serialVersionUIDList = Lists.newArrayList();

	public void init() {
		Validate.notNull(this.projectMapper);

		Validate.notNull(this.outputDir);
	}

	private void makeFile(final String templateString, final String templateName) throws Exception {
		DefaultTemplate.log.info(templateString);

		DefaultTemplate.log.info(templateName);

		final String filePathName = com.fand.template.utils.FreemarkerTemplateUtils.processString(templateString, this.contextMap);

		DefaultTemplate.log.info(filePathName);

		final File file = new File(filePathName);

		if (file.exists()) {
			DefaultTemplate.log.info("file.exists():true");

			if (this.overrideFile) {
				FileUtils.deleteQuietly(file);
			} else {
				return;
			}
		}

		final freemarker.template.Template template = this.configuration.getTemplate(templateName);

		this.modelPutSerialVersionUID();

		final String data = FreeMarkerTemplateUtils.processTemplateIntoString(template, this.model);

		FileUtils.writeStringToFile(file, data);
	}

	private void markCommonService() throws Exception {
		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}common${separator!}service${separator!}CommonService.java";

		this.makeFile(javaTemplateString, "CommonService.java.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}common${separator!}service${separator!}DefaultCommonService.java";

		this.makeFile(javaTemplateString, "DefaultCommonService.java.ftl");
	}

	private void markCommonSpringConfig() throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}common${separator!}applicationContext-Common.xml";

		this.makeFile(javaTemplateString, "applicationContext-Common.ftl");
	}

	private void markController(final String businessCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}controller${separator!}${filename!}Controller.java";

		this.makeFile(javaTemplateString, "Controller.ftl");
	}

	private void markDao(final String businessCode) throws Exception {
		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dao${separator!}${filename!}Dao.java";

		this.makeFile(javaTemplateString, "Dao.java.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dao${separator!}Jdbc${filename!}Dao.java";

		this.makeFile(javaTemplateString, "JdbcDao.java.ftl");
		//
		// javaTemplateString =
		// "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dao${separator!}${filename!}Dao.java";
		//
		// this.makeFile(javaTemplateString, "ExtendDao.java.ftl");
	}

	private void markDto(final String entityCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dto${separator!}${filename!}Dto.java";

		this.makeFile(javaTemplateString, "Dto.java.ftl");
	}

	private void markPo(final String entityCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}po${separator!}${filename!}.java";

		this.makeFile(javaTemplateString, "Po.java.ftl");
	}

	private void markService(final String businessCode) throws Exception {
		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}service${separator!}${filename!}Service.java";

		this.makeFile(javaTemplateString, "Service.java.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}service${separator!}Default${filename!}Service.java";

		this.makeFile(javaTemplateString, "DefaultService.java.ftl");
	}

	private void markSpringConfig(final String businessCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}${moduleMapperKey!}${separator!}applicationContext-${filename!}.xml";

		this.makeFile(javaTemplateString, "applicationContext-service.ftl");
	}

	private void markSQL(final String entityCode) throws Exception {
		this.contextMap.put("camelCaseEntityCode", com.fand.utils.StringUtils.toCamelCase(entityCode));

		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${camelCaseExtendName!}${separator!}${moduleMapperKey!}${separator!}${camelCaseEntityCode!}${separator!}update${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "updateBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${camelCaseExtendName!}${separator!}${moduleMapperKey!}${separator!}${camelCaseEntityCode!}${separator!}select${filename!}By1ForMappedObjectList.ftl";

		this.makeFile(javaTemplateString, "selectBy1ForMappedObjectList.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${camelCaseExtendName!}${separator!}${moduleMapperKey!}${separator!}${camelCaseEntityCode!}${separator!}select${filename!}By1ForObject.ftl";

		this.makeFile(javaTemplateString, "selectBy1ForObject.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${camelCaseExtendName!}${separator!}${moduleMapperKey!}${separator!}${camelCaseEntityCode!}${separator!}where${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "whereBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${camelCaseExtendName!}${separator!}${moduleMapperKey!}${separator!}${camelCaseEntityCode!}${separator!}insert${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "insertBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${camelCaseExtendName!}${separator!}${moduleMapperKey!}${separator!}${camelCaseEntityCode!}${separator!}delete${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "deleteBy1.ftl");
	}

	private void markVo(final String businessCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}vo${separator!}${filename!}Vo.java";

		this.makeFile(javaTemplateString, "Vo.java.ftl");
	}

	private void modelPutSerialVersionUID() {
		String serialVersionUID = RandomStringUtils.randomNumeric(18);

		while (StringUtils.startsWithAny(serialVersionUID, new String[] { "0", "9" })
				|| this.serialVersionUIDList.contains(serialVersionUID)) {
			serialVersionUID = RandomStringUtils.randomNumeric(18);
		}

		this.serialVersionUIDList.add(serialVersionUID);

		this.model.put("serialVersionUID", serialVersionUID);
	}

	@Override
	public void run(final Map<String, PoMapper> poMapperMap) throws Exception {
		if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperMap())) {
			return;
		}

		this.poMapperMap = poMapperMap;

		final String packageDir = StringUtils.replace(this.projectMapper.getPackageCode(), ".", File.separator);

		this.contextMap.put("outputDir", this.outputDir);

		this.contextMap.put("separator", File.separator);

		this.contextMap.put("packageDir", packageDir);

		this.model.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap().entrySet()) {
			moduleMapperEntry.getValue().setCode(moduleMapperEntry.getKey());

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue().getBusinessMap().entrySet()) {
				businessMapperEntry.getValue().setCode(businessMapperEntry.getKey());
			}
		}

		this.model.put("projectMapper", this.projectMapper);

		this.model.put("packageCode", this.projectMapper.getPackageCode());

		this.model.put("extendName", this.extendName);

		this.contextMap.put("extendName", this.extendName);

		final String camelCaseExtendName = com.fand.utils.StringUtils.toCamelCase(this.extendName);

		this.model.put("camelCaseExtendName", camelCaseExtendName);

		this.contextMap.put("camelCaseExtendName", camelCaseExtendName);

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap().entrySet()) {
			moduleMapperEntry.getValue().setCode(moduleMapperEntry.getKey());

			this.contextMap.put("moduleMapperKey", moduleMapperEntry.getKey());

			this.model.put("moduleMapperKey", moduleMapperEntry.getKey());

			this.model.put("moduleMapper", moduleMapperEntry.getValue());

			if (CollectionUtils.isEmpty(moduleMapperEntry.getValue().getBusinessMap())) {
				continue;
			}

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue().getBusinessMap().entrySet()) {
				if (!CollectionUtils.isEmpty(businessMapperEntry.getValue().getEntityList())) {
					for (final String entityCode : businessMapperEntry.getValue().getEntityList()) {
						if (!this.poMapperMap.containsKey(entityCode)) {
							continue;
						}

						final PoMapper poMapper = this.poMapperMap.get(entityCode);

						this.model.put("poMapper", poMapper);

						this.contextMap.put("filename", entityCode);

						this.markPo(entityCode);

						this.markDto(entityCode);

						this.markSQL(entityCode);
					}
				}

				this.setBusiness(businessMapperEntry.getValue());

				this.markVo(businessMapperEntry.getKey());

				this.markDao(businessMapperEntry.getKey());

				this.markService(businessMapperEntry.getKey());

				this.markSpringConfig(businessMapperEntry.getKey());

				this.markController(businessMapperEntry.getKey());
			}

			this.model.put("projectMapper", this.projectMapper);

			this.markCommonService();

			this.markCommonSpringConfig();
		}
	}

	private void setBusiness(final BusinessMapper businessMapper) {
		this.contextMap.put("filename", businessMapper.getCode());

		this.model.put("businessMapper", businessMapper);

		this.model.put("businessCode", businessMapper.getCode());

		final String camelCaseBusinessCode = com.fand.utils.StringUtils.toCamelCase(businessMapper.getCode());

		this.contextMap.put("camelCaseBusinessCode", camelCaseBusinessCode);

		this.model.put("camelCaseBusinessCode", camelCaseBusinessCode);

		final List<String> entityList = Lists.newArrayList();

		final List<PoMapper> poMapperList = Lists.newArrayList();

		if (!CollectionUtils.isEmpty(businessMapper.getEntityList())) {
			for (final String entityCode : businessMapper.getEntityList()) {
				if (!this.poMapperMap.containsKey(entityCode)) {
					continue;
				}

				entityList.add(entityCode);

				final PoMapper poMapper = this.poMapperMap.get(entityCode);

				poMapperList.add(poMapper);
			}
		}

		this.model.put("entityList", entityList);

		this.model.put("poMapperList", poMapperList);
	}

	public void setConfiguration(final Configuration configuration) {
		this.configuration = configuration;
	}

	public void setExtendName(final String extendName) {
		this.extendName = extendName;
	}

	public void setOutputDir(final String outputDir) {
		this.outputDir = outputDir;
	}

	public void setOverrideFile(final boolean overrideFile) {
		this.overrideFile = overrideFile;
	}

	public void setProjectMapper(final ProjectMapper projectMapper) {
		this.projectMapper = projectMapper;
	}
}
